# This file is part of libvcc.py.

# libvcc.py is free software: you can redistribute it and/or modify it under the terms of the GNU General 
# Public License as published by the Free Software Foundation, either version 3 of the License, or (at 
# your option) any later version.

# libvcc.py is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the 
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 
# License for more details.

# You should have received a copy of the GNU General Public License along with libvcc.py. If not, see 
# <https://www.gnu.org/licenses/>. 

from __future__ import annotations

import socket
import struct
import ipaddress
import asyncio
import logging
from types import TracebackType
from typing import Any, cast, overload

from .constants import *

class Connection:
    """The low-level api of vcc socket"""
    def __init__(self, ip: str, port: int) -> None:
        try:
            self.version = ipaddress.ip_address(ip).version
        except ValueError:
            try:
                ip = socket.gethostbyname(ip)
                ip_address = ipaddress.ip_address(ip)
            except socket.error:
                raise InvalidIPError("Not a valid host or IP address") from None
        self.ip = ip
        self.port = port
    async def init(self) -> Connection:
        loop = asyncio.get_event_loop()
        if self.version == 4:
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.sock.setblocking(False)
            self.sock.bind(("0.0.0.0", 0))
            await loop.sock_connect(self.sock, (self.ip, self.port))
        elif self.version == 6:
            self.sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
            self.sock.setblocking(False)
            self.sock.bind(("::", 0))
            await loop.sock_connect(self.sock, (self.ip, self.port, 0, 0))
        return self

    async def send(
        self, *, 
        magic: int, 
        type: int, 
        uid: int, 
        session: int, 
        flags: int, 
        username: str, 
        msg: str | bytes,
    ) -> None:
        loop = asyncio.get_event_loop()
        logging.debug(f"msgic: {magic}, type: {type}, uid: {uid}, session: {session}, flags: {flags}, username: {username}, msg: {msg!r}")
        if isinstance(msg, str):
            msg += "\0"
            msg = msg.encode()
        await loop.sock_sendall(self.sock, request_struct.pack(
            socket.htonl(magic), 
            socket.htonl(type),
            socket.htonl(uid),
            socket.htonl(session),
            flags,
            (username + "\0").encode(),
            msg
        ))

    async def send_relay(self, *, magic: int, uid: int, session: int, username: str, msg: str, visible: str) -> None:
        loop = asyncio.get_event_loop()
        length = relay_header_struct.size + len(msg.encode()) + 1
    
        logging.debug(f"msgic: {magic}, uid: {uid}, session: {session}, length: {length}, username: {username}, msg: {msg}, visible: {visible}")
        await loop.sock_sendall(self.sock, relay_header_struct.pack(
            socket.htonl(magic), 
            socket.htonl(REQ.REL_MSG), 
            socket.htonl(length),
            socket.htonl(uid),
            socket.htonl(session), 
            username.encode() + b"\0",
            visible.encode() + b"\0"
        ))

        await loop.sock_sendall(self.sock, msg.encode() + b"\0")

    @classmethod
    @overload
    def to_normal(cls, content: int) -> int: ...

    @classmethod
    @overload
    def to_normal(cls, content: bytes) -> str: ...

    @classmethod
    def to_normal(cls, content: int | bytes) -> int | str:
        if isinstance(content, int):
            return socket.ntohl(content)
        else:
            return content.decode(errors="ignore").split("\x00")[0]

    async def recv(self) -> tuple[RawRequest, Request] | tuple[RawRelay, Relay]:
        loop = asyncio.get_event_loop()

        raw_recv_data = await loop.sock_recv(self.sock, relay_header_struct.size)
        relay_header_data = RawRelayHeader(*relay_header_struct.unpack(raw_recv_data))

        if socket.ntohl(relay_header_data.magic) != VCC_MAGIC:
            # handle a relay response
            logging.debug(f"raw relay header content: {repr(relay_header_data)}")
            size = socket.ntohl(relay_header_data.size) - relay_header_struct.size
            logging.debug(f"{size}")
            raw_recv_data += await loop.sock_recv(self.sock, size)
            raw_relay_data = RawRelay(*struct.unpack(VCC_RELAY_HEADER_FORMAT + f"{size}s", raw_recv_data))
            relay_tuple_data = Relay(**{key: cast(Any, self.to_normal(value)) for key, value in raw_relay_data._asdict().items()})
            logging.debug(f"raw relay content: {repr(raw_relay_data)}")
            logging.debug(f"relay content: {repr(relay_tuple_data)}")
            return raw_relay_data, relay_tuple_data
        
        # handle a normal response
        raw_recv_data += await loop.sock_recv(self.sock, REQ_SIZE - relay_header_struct.size)
        tuple_data = request_struct.unpack(raw_recv_data)
        raw_request = RawRequest(*tuple_data)
        request = Request(**{key: cast(Any, self.to_normal(value) if key != "flags" else value) for key, value in raw_request._asdict().items()})
        logging.debug(f"raw request content: {repr(raw_request)}")
        logging.debug(f"request content: {repr(request)}")
        return raw_request, request
         
            

